package solver;

import java.util.Random;

import model.Problem;
import model.Solution;

public class SimulatedAnnealingSolver extends BaseHeuristicSolver{

	static final int ALLOW_ONLY_IMPROVE=30;
	double initialTemperature=10.0;
	double finalTemperature=1E-3;
	int numIterinTemp = 5;
	long maxIter = (long)1E5;
	double temperature = initialTemperature;
	double lastSuccesfullTemp=initialTemperature;
	double b;
	
	
	public SimulatedAnnealingSolver(Problem problem, Solution initialSolution,
			Solution newSolution) {
		super(problem, initialSolution, newSolution);
		name = "SA";
	}
	
	public SimulatedAnnealingSolver(Problem problem, Solution initialSolution) {
		super(problem, initialSolution);
		name = "SA";
	}
		
	@Override
	public int solveWithTimeLimit(int solution_time) throws Exception {
		MAX_SOLUTION_TIME = solution_time;
		startSolution();
		reset();
		int c = ALLOW_ONLY_IMPROVE;
		boolean allow_inferior = false;
		double delta, acceptance;

		while (shouldStop()){
			if (!makeNextMove())
				continue;
			iterations++;

			// checking the acceptance condition
			if (potential_cost < current_cost) {
				if (saveCurrentToBest()) {
					// System.out.printf("%d:Better cost found %d from %d\n",
					// iterations, potential_cost,
					// best_cost);
					lastSuccesfullTemp = temperature;
					finishIteration(true, true);
				}
				else
					finishIteration(true, false);
				current_cost = potential_cost;
				cool();
			}
			else {
				delta = ((potential_cost-best_cost)/(0.1*(double)best_cost))*initialTemperature;

				c--;
				if (c <= 0) {
					allow_inferior = true;
				}
				//If delta == 0 the move is accepted ie allow equal cost moves
				acceptance = Math.exp(-delta / temperature);
//				System.out.println("best_cost:"+best_cost+" new cost:"+potential_cost+" acceptance:"+acceptance);
				if ((allow_inferior) && (getRandom().nextDouble() < acceptance)) {
//				if ((random.nextDouble() < acceptance)) {
					if(delta!=0.0){
						c = ALLOW_ONLY_IMPROVE;
						allow_inferior = false;
					}
					finishIteration(true,false);
					current_cost = potential_cost;
				}
				else {
					finishIteration(false,false);
				}
			}//finish decision
			
			if ((temperature < finalTemperature) && noImprove>=1000) {
				System.out.print("REHEAT:");
				reheat();
				System.out.println(" temperature = "+temperature);
				setCurrentSolution(getBestSolution().copy());
				Problem.VERBOSE = oldVerbose;
				startSolution();
				//					no_improvement = 0;
			}
			
			// each 100000 steps we output the current and the best costs
			if ((iterations % 100000) == 0) 										
			{
				 System.out.printf(
				 "%10d current (%d) best (%d) noImprovement=%d temperature=%f\n",
				 iterations, current_cost, best_cost, noImprove, temperature);
			}

		}//while

		finishSolution(true);
		return chrono.getElapsedTimeInSeconds();
	}


	public void reset() {
		b = ((initialTemperature - finalTemperature)*numIterinTemp)/(maxIter*initialTemperature*finalTemperature);
		System.out.println("b="+b);
		temperature = initialTemperature;	
	}

	protected void cool() {
		updateTemperatureLinear();
		//			updateTemperatureLundyandMees();
	}

	private void updateTemperatureLinear(){
		temperature = temperature*0.999;
	}
	/**
	 * Implements the non-linear temperature update of
	 * Lundy, M., & Mees, A. (1986). Convergence of an annealing algorithm. Mathematical Programming, 34, 111–124
	 * t0:
	 */
	private void updateTemperatureLundyandMees(){

		temperature = temperature / (1+b*temperature);
	}

	private void reheat(){
		simplereheat();
		//			reheatTemperatureLundyandMees();
	}

	private void simplereheat(){
		temperature = initialTemperature ;
	}

	private void reheatTemperatureLundyandMees(){
		temperature = temperature / (1-b*temperature);
		if(temperature>lastSuccesfullTemp)
			temperature = lastSuccesfullTemp;
	}		

}
